/*
 * Copyright (c) 2008 Benoit Bouchez, Nicolas Giard
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.noocodecommit.wicket.stuff.jquery;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.markup.html.IHeaderContributor;
import org.apache.wicket.markup.html.IHeaderResponse;
import org.apache.wicket.markup.html.WebPage;

import com.noocodecommit.wicket.stuff.jquery.exception.JQCoreNotInitializedException;
import com.noocodecommit.wicket.stuff.jquery.plugin.IJQPluginComponent;
import com.noocodecommit.wicket.stuff.jquery.plugin.JQPlugin;
import com.noocodecommit.wicket.stuff.jquery.theme.ITheme;

/**
 * JQueryCore is the easiest way to insert JQuery library in you WebPages.<br>
 * It generates all HTML and Javascript needed in the head section to run JQuery<br>
 * Once it's created, you can add JQueryPlugins on it.
 * 
 * @author Benoit Bouchez
 * 
 */
public class JQCore implements Serializable
{
	/** Standard library selector ; usefull in dev mode */
	public static String LIBMODE_STANDARD = "";
	/** Packed library selector */
	public static String LIBMODE_PACKED = ".packed";
	/** Minified library selector */
	public static String LIBMODE_MINIFIED = ".min";

	/** Web Page */
	private WebPage page;
	/** Default theme */
	private ITheme theme = null;
	
	/** Plugin collection */
	private HashMap<String, JQPlugin> plugins;
	/** Scripts before init JQuery Core */
	private ArrayList<String> beforePluginInitScripts;
	/** Scripts after init JQuery Core */
	private ArrayList<String> afterPluginInitScripts;
	/** JQuery Core instance */
	private static JQCore instance;
	/** Default library selector */
	private static String libraryMode = LIBMODE_STANDARD;

	/**
	 * Constructor
	 * 
	 * @param page
	 *            WebPage on which you need to use JQuery library
	 */
	private JQCore(WebPage page)
	{
		super();
		this.page = page;
		this.plugins = new HashMap<String, JQPlugin>();
		this.beforePluginInitScripts = new ArrayList<String>();
		this.afterPluginInitScripts = new ArrayList<String>();
		this.bindPage(page);
	}

	/**
	 * Init an instance of JQCore
	 * 
	 * @param page
	 *            WebPage on which you need to use JQuery library
	 * @return the current instance of JQCore
	 */
	public static JQCore init(WebPage page)
	{
		synchronized (JQCore.class)
		{
			if (instance == null)
			{
				instance = new JQCore(page);
			}
			else
			{
				instance.page = page;
				instance.plugins = new HashMap<String, JQPlugin>();
				instance.beforePluginInitScripts = new ArrayList<String>();
				instance.afterPluginInitScripts = new ArrayList<String>();
				instance.bindPage(page);
			}
		}
		return instance;
	}

	/**
	 * @return the current JQCore instance
	 */
	public static JQCore get()
	{
		if (instance == null)
		{
			throw new JQCoreNotInitializedException();
		}
		else
		{
			return instance;
		}
	}

	/**
	 * @return the current WebPage
	 */
	public WebPage getWebPage()
	{
		return this.page;
	}

	/**
	 * Registers a WebPage in order to insert the JQuery library on it.<br>
	 * Also register JQuery Enchant libraries
	 * 
	 * @param page
	 *            WebPage on which you need to insert JQuery library
	 */
	private void bindPage(WebPage page)
	{
		page.add(HeaderContributor.forJavaScript(JQCore.class, "lib/jquery" + libraryMode + ".js"));
	}

	/**
	 * Registers a plugin to use with the JQuery library
	 * 
	 * @param plugin
	 *            JQueryPlugin to register
	 */
	public void bindPlugin(JQPlugin plugin)
	{
		plugin.initialize(this.page, plugin.getClass());
		if (!this.plugins.containsKey(plugin.getKey()))
		{
			this.plugins.put(plugin.getKey(), plugin);
		}
	}

	/**
	 * Force plugin bind when resources insert is needed
	 * 
	 * @param plugin
	 *            JQueryPlugin to rebind
	 */
	public void rebindPlugin(JQPlugin plugin)
	{
		plugin.initialize(this.page, plugin.getClass());
	}

	/**
	 * Get the all plugins
	 * 
	 * @return HashMap<String, JQPlugin>
	 */
	public HashMap<String, JQPlugin> getPlugins()
	{
		return plugins;
	}

	/**
	 * Gets a binded plugin
	 * 
	 * @param key
	 *            plugin unique id
	 * @return a JQPlugin object
	 */
	public JQPlugin getPlugin(String key)
	{
		return plugins.get(key);
	}

	/**
	 * Inserts in the head section of the JQueryCore's page all plugins javascript functions
	 */
	public void bindOnDocumentReadyFunction()
	{
		// ResourceReference jqueryDocumentReady = new ResourceReference("jqueryDocumentReady")
		// {
		// protected Resource newResource()
		// {
		// return new JQDocumentReadyResource(JQCore.this);
		// }
		// };
		// HeaderContributor javascriptHeaderContributor = JQDocumentReadyResource.createHeaderContributor(jqueryDocumentReady);
		// page.add(javascriptHeaderContributor);

		HeaderContributor contributor = new HeaderContributor(new IHeaderContributor()
		{
			public void renderHead(IHeaderResponse response)
			{
				StringBuffer buffer = new StringBuffer();
				buffer.append("<script type=\"text/javascript\">\n");
				buffer.append("\t$(document).ready(function(){\n");

				// before plugin init scripts
				if (beforePluginInitScripts.size() > 0)
				{
					buffer.append("\t\t// -----------------------------------\n");
					buffer.append("\t\t// -- JQ4W - BeforePluginInitScripts -\n");
					buffer.append("\t\t// -----------------------------------\n");
				}
				for (int i = 0; i < beforePluginInitScripts.size(); i++)
				{
					buffer.append("\t\t" + beforePluginInitScripts.get(i) + "\n");
				}

				// Render jquery function calls for HTML elements with
				// specific behavior
				if (plugins.size() > 0)
				{
					buffer.append("\t\t// -----------------------------------\n");
					buffer.append("\t\t// -- JQ4W - Plugins Init            -\n");
					buffer.append("\t\t// -----------------------------------\n");
				}
				for (Iterator<String> i = plugins.keySet().iterator(); i.hasNext();)
				{
					String key = i.next();
					// Plugin init is done with 2 differents ways
					// If PLUGIN_INIT_ONCE : we init the plugin only once and after widgets with ajax behaviors attached one by one
					// If PLUGIN_INIT_EACH_WIDGET : widgets are init one by one. Ajax support is no longer watched.
					if (plugins.get(key).getInitMode() == JQPlugin.PLUGIN_INIT_ONCE)
					{
						// Plugin init
						if ((plugins.get(key).getPluginReadyScript() != null) && (plugins.get(key).getInitOnceSelector() != null))
						{
							buffer.append("\t\t" + plugins.get(key).getPluginReadyScript() + "\n");
						}
						// Widgets with ajax behavior init
						for (int j = 0; j < plugins.get(key).getAttachedComponentsForDocumentReadySupportingAjax().size(); j++)
						{
							IJQPluginComponent component = plugins.get(key).getAttachedComponentsForDocumentReadySupportingAjax().get(j);
							if (component.getWidgetReadyScript(true).length() > 0)
							{
								buffer.append("\t\t" + component.getWidgetReadyScript(true) + "\n");
							}
						}
					}
					else if (plugins.get(key).getInitMode() == JQPlugin.PLUGIN_INIT_EACH_WIDGET)
					{
						// Widgets init
						for (int j = 0; j < plugins.get(key).getAttachedComponentsForDocumentReady().size(); j++)
						{
							IJQPluginComponent component = plugins.get(key).getAttachedComponentsForDocumentReady().get(j);
							if (component.getWidgetReadyScript(false).length() > 0)
							{
								buffer.append("\t\t" + component.getWidgetReadyScript(false) + "\n");
							}
						}
					}
				}

				// after plugin init scripts
				if (afterPluginInitScripts.size() > 0)
				{
					buffer.append("\t\t// -----------------------------------\n");
					buffer.append("\t\t// -- JQ4W - AfterPluginInitScripts  -\n");
					buffer.append("\t\t// -----------------------------------\n");
				}
				for (int i = 0; i < afterPluginInitScripts.size(); i++)
				{
					buffer.append("\t\t" + afterPluginInitScripts.get(i) + "\n");
				}

				buffer.append("\t});\n");
				buffer.append("</script>\n");
				response.renderString(buffer.toString());
			}
		});
		page.add(contributor);
	}

	/**
	 * 
	 * @return scripts to insert before JQuery inits
	 */
	public ArrayList<String> getBeforePluginInitScripts()
	{
		return beforePluginInitScripts;
	}

	/**
	 * 
	 * @return scripts to insert after JQuery inits
	 */
	public ArrayList<String> getAfterPluginInitScripts()
	{
		return afterPluginInitScripts;
	}

	/**
	 * @return the JQCore ITheme
	 */
	public ITheme getTheme()
	{
		return theme;
	}

	/**
	 * Sets the JQCore ITheme
	 * 
	 * @param theme
	 *            the theme
	 */
	public void setTheme(ITheme theme)
	{
		this.theme = theme;
	}

	/**
	 * 
	 * @return the JQCore library mode selector
	 */
	public static String getLibraryMode()
	{
		return libraryMode;
	}

	/**
	 * Sets the JQCore library mode selector
	 * 
	 * @param libraryMode
	 *            LIBMODE_STANDARD (default), LIBMODE_PACKED, LIBMODE_MINIFIED
	 */
	public static void setLibraryMode(String libraryMode)
	{
		JQCore.libraryMode = libraryMode;
	}
}